package org.harsh.transmission.common;

import cn.hutool.core.codec.Base64;
import cn.hutool.core.lang.UUID;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.bouncycastle.util.Arrays;
import org.harsh.common.Config;
import org.harsh.common.Constants;
import org.harsh.common.Util;
import org.harsh.transmission.domain.TransMsg;
import org.harsh.transmission.domain.TransTask;

import java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class CustomFile implements ITransable {
    // 消息类型 Constants.MessageType
    private byte msgType;
    // 传输类型 0 - 单播 1 - 组播 2 - 广播 3 - 单向
    private byte transType;
    // 数据编号
    private long msgId;
    // 节点编号
    private int fromNode;
    // 数据来源 0 - 用户 1 - 业务系统
    private byte fromType;
    // 数据来源ID
    private int fromId;
    // 接收用户信息
    private int recvId;
    // 文件长度
    private long fileSize;
    // 16字节
    private byte[] fileMd5;

    private String fileName;
    private String filePath;
    // 偏移
    private long startpos;
//    private int chunkSize;
    private byte[] chunkMd5;
    private byte[] chunkData;

    private byte recvCode;
    private long recvSize;

    public enum RecvDataCode {
        ;
        public static byte SUCCESS = 1;
        public static byte ERROR = 0;
    }

    /**
     * 根据数据和任务初始化File信息
     * @param msg
     * @param task
     */
    public CustomFile(TransMsg msg, TransTask task, int recvUserId, String filePath, String fileName) {
        setMsgType((byte) org.harsh.common.Constants.MessageType.TRANSFILEHEAD.getCode());
        setTransType(task.getTaskType().byteValue());
        setMsgId(msg.getMsgId());
        setFromNode(msg.getFromNode().intValue());
        setFromType(msg.getFromType().byteValue());
        setFromId(msg.getFromId().intValue());
        setRecvId(recvUserId);
        setFileMd5(Base64.decode(msg.getMsgMd5()));
//        setFileMd5(msg.getMsgMd5().getBytes(Config.DEFAULT_CHARSET));
        setFilePath(filePath);
        setFileName(fileName);
        setFileSize(msg.getMsgSize());
        setStartpos(0l);
    }

    public CustomFile(byte msgType, long msgId, byte recvCode, long startPos, long recvSize) {
        this.msgType = msgType;
        this.msgId = msgId;
        this.recvCode = recvCode;
        this.startpos =  startPos;
        this.recvSize = recvSize;
    }

    /**
     *   头部类型 1字节         *   传输类型 1字节         *   数据编号 8字节         *   来源节点 4字节
     *   来源类型 1字节         *   来源编号 4字节         *   接收信息 4字节         *   长度信息 8字节
     *   MD5信息 16字节        *   文件名称长度 2字节         *   文件名称
     */
    /**
     * 文件头部描述数据包
     * @return
     * @throws IOException
     */
    public byte[] buildFileHeadMsg() throws IOException {
        ByteBuffer buf = ByteBuffer.allocate(49);
        buf.put(msgType);
        buf.put(transType);
        buf.putLong(msgId);
        buf.putInt(fromNode);
        buf.put(fromType);
        buf.putInt(fromId);
        buf.putInt(recvId).putLong(fileSize).put(fileMd5);
        byte[] filePathArr = fileName.getBytes(Config.DEFAULT_CHARSET);
        buf.putShort((short) filePathArr.length);
        return Arrays.concatenate(buf.array(), filePathArr);
    }

    /**
     * 文件数据块信息
     * @return
     */
    public byte[] buildFileDataMsg() {
        ByteBuffer buf = ByteBuffer.allocate(37);
        buf.put(msgType);
        buf.putLong(msgId);
        buf.put(chunkMd5);
        buf.putLong(startpos);
        buf.putInt(chunkData.length);
        return Arrays.concatenate(buf.array(), chunkData);
    }

    /**
     * 文件响应信息
     * @return
     */
    public byte[] buildFileAckMsg() {
        ByteBuffer buf = ByteBuffer.allocate(26);
        buf.put(msgType);
        buf.putLong(msgId);
        buf.put(recvCode);
        buf.putLong(startpos);
        buf.putLong(recvSize);
        return buf.array();
    }
    /**
     * 文件结束信息
     */
    public byte[] buildFileOverMsg() {
        ByteBuffer buf = ByteBuffer.allocate(9);
        buf.put(msgType);
        buf.putLong(msgId);
        return buf.array();
    }


    public CustomFile(File tmp) throws IOException {
        this.msgType = Constants.MessageType.TRANSFILEHEAD.getCode();
        this.msgId = Math.abs(UUID.randomUUID().hashCode());
        this.filePath = tmp.getAbsolutePath();
        this.fileName = tmp.getName();
        this.fileMd5 = Util.fileMD5(tmp);
        this.fileSize = tmp.length();
        this.startpos = 0l;
    }


    public CustomFile(String filePath) throws IOException {
        this.filePath = filePath;
        File tmp = new File(filePath);
        this.fileName = tmp.getName();
        this.fileMd5 = Util.fileMD5(tmp);
        this.fileSize = tmp.length();
        this.startpos = 0l;
    }

    // customFile Head
    public CustomFile(byte msgType, byte[] data) {
        this.msgType = msgType;
        this.setTransType(data[0]);
        this.setMsgId(Util.bytes2Long(data, 1));
        this.setFromNode(Util.bytes2int(data, 9));
        this.setFromType(data[13]);
        this.setFromId(Util.bytes2int(data,14));
        this.setRecvId(Util.bytes2int(data,18));
        this.setFileSize(Util.bytes2Long(data,22));
        this.setFileMd5(Arrays.copyOfRange(data, 30, 46));
        short pathStrLen = Util.bytes2short(data, 46);
        byte[] pathArr = Arrays.copyOfRange(data, 48, 48 + pathStrLen);
        this.fileName = new String(pathArr, Config.DEFAULT_CHARSET);
    }

    public CustomFile(byte msgType, long msgId, long startPos, byte[] data) throws IOException {
        this.msgType = msgType;
        this.msgId = msgId;
        this.startpos = startPos;
        this.chunkData = data;
        this.chunkMd5 = Util.byteArrMD5(data);
    }

    // ack customFile Data or Over
    public CustomFile(byte msgType, long msgId) throws IOException {
        this.msgType = msgType;
        this.msgId = msgId;
    }

    /**
     * 文件头部描述
     * @return
     * @throws IOException
     */
    @Deprecated
    public byte[] buildFileHead() throws IOException {
        ByteBuffer buf = ByteBuffer.allocate(43);
        buf.put(msgType);
        buf.putLong(msgId);
        // 文件头部描述：size + md5 + fileNameLen + fileName
        buf.putLong(fileSize);
        buf.put(fileMd5);
        buf.putLong(startpos);
        byte[] fileNameArr = fileName.getBytes(Config.DEFAULT_CHARSET);
        buf.putShort((short) fileNameArr.length);
        return Arrays.concatenate(buf.array(), fileNameArr);
    }

    /**
     *
     * @return
     */
    @Deprecated
    public byte[] buildFileOver() {
        ByteBuffer buf = ByteBuffer.allocate(9);
        buf.put(msgType);
        buf.putLong(msgId);
        return buf.array();
    }

    @Deprecated
    public byte[] buildFileData() {
        ByteBuffer buf = ByteBuffer.allocate(37);
        buf.put(msgType);
        buf.putLong(msgId);
        buf.put(chunkMd5);
        buf.putLong(startpos);
        buf.putInt(chunkData.length);
        return Arrays.concatenate(buf.array(), chunkData);
    }

    public void parseFileHead(byte[] data) {
        this.transType = data[0];
        this.msgId = Util.bytes2Long(data, 1);
        this.fileSize = Util.bytes2Long(data, 0);
        // MD5长度16字节
        this.fileMd5 = Arrays.copyOfRange(data, 8,24);
        this.startpos = Util.bytes2Long(data, 24);
        short fnameLen = Util.bytes2short(data, 32);
        byte[] fileNameArr = Arrays.copyOfRange(data, 34, 34 + fnameLen);
        this.fileName = new String(fileNameArr, Config.DEFAULT_CHARSET);
    }

    public void parseFileData(byte[] data) {
        this.chunkMd5 = Arrays.copyOfRange(data, 0,16);
        this.startpos = Util.bytes2Long(data, 16);
        int dataLen = Util.bytes2int(data, 24);
        this.chunkData = Arrays.copyOfRange(data, 28,  28 + dataLen);
    }

    @Override
    public byte[] buildBytes() throws IOException {
        if(msgType == Constants.MessageType.TRANSFILEHEAD.getCode()) {
            return buildFileHeadMsg();
        } else if (msgType == Constants.MessageType.TRANSFILEDATA.getCode()) {
            return buildFileDataMsg();
        } else if (msgType == Constants.MessageType.TRANSFILEOVER.getCode() || msgType == Constants.MessageType.ACKTRANSFILEOVER.getCode() ) {
            return buildFileOverMsg();
        } else if (msgType == Constants.MessageType.ACKTRANSFILE.getCode()) {
            return buildFileAckMsg();
        }
        return null;
    }
}
